/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.basicServices.discovery;

import hermesV2.Place;
import hermesV2.PlaceAddress;
import hermesV2.PlaceDescriptor;
import hermesV2.ServiceDescriptor;
import hermesV2.agent.AbstractProtocol;
import hermesV2.agent.Identificator;

import hermesV2.core.id.Identify;
import hermesV2.core.net.DescriptorIO;

import hermesV2.util.Descriptor;
import hermesV2.util.HermesLogger;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * The discovery manager has the responsability to make visible service agents
 * present into the place. It offers a way to register or to search new services
 * into the place.
 * 
 * @author Diego Bonura
 */
public class DiscoveryManager implements DiscoveryServiceAgents, DiscoveryPlaces, Update, Runnable {
    /**
     * The hashtable contains Id and descriptions of every service agents
     * registered
     */
    private Hashtable<Identificator, String[]> localServicesTable;

    /**
     * The hashtable contains Id and descriptions of every service agents
     * discovered on the network
     */
    private Hashtable <String, ServiceDescriptor> remoteServicesTable;

    /**
     * The hashtable contains Id and protocol of every service agents registered
     */
    private Hashtable <Identificator, AbstractProtocol> servicesProtocol;

    /**
     * The hashtable contains remte placeAddress of every service place found.
     */
    private Hashtable <String, PlaceDescriptor> placesTable;

    /** The Identify interface of the Id component. */
    private Identify identify;

    /** The PlaceDescriptorIO interface of the SendReceive component. */
    private DescriptorIO descriptorIO;

    /**
     * Creates a new DiscoveryManager object.
     */
    public DiscoveryManager() {
        localServicesTable = new Hashtable <Identificator, String[]>();
        remoteServicesTable = new Hashtable<String, ServiceDescriptor> ();
        servicesProtocol = new Hashtable<Identificator, AbstractProtocol>();
        placesTable = new Hashtable<String, PlaceDescriptor> ();
    }

    /**
     * Registers to this component the Identify interface from the Core.Id
     * component.
     * 
     * @param identify
     *            The Identify interface of the Id component.
     */
    public void setIdentify(Identify identify) {
        this.identify = identify;
    }

    /**
     * Registers to this component the PlaceDescriptorIO interface from the
     * Core.SendReceive component.
     * 
     * @param descriptorIO
     *            The PlaceDescriptorIO interface of the SendReceive component.
     */
    public void setPlaceDescriptorIO(DescriptorIO descriptorIO) {
        this.descriptorIO = descriptorIO;
    }

    /**
     * Returns the identificator of a Service agent registered in the discovery
     * component. The match is made with the key word.
     * 
     * @param keyWord
     *            The kay word to match.
     * 
     * @return The Service Agent Identificator corresponding to the key word
     *         otherwise if no service agent match null.
     */
    public Identificator getServiceId(String keyWord) {
        String[] keyWords = new String[1];
        keyWords[0] = keyWord;
        return getServiceId(keyWords);
    }

    /**
     * Searches the service agent into the discovery component and returns the
     * first agent that match (the match isn't case sensitive) the key words
     * array.
     * 
     * @param keyWordArray
     *            An array of words to search into the description of every
     *            service agents registered into the component.
     * 
     * @return The identificator of the service agent matched or null if no
     *         service is found.
     */
    public Identificator getServiceId(String[] keyWordArray) {
        Vector<Identificator> matchingKeys = new Vector<Identificator>();
        Enumeration<Identificator> keys = localServicesTable.keys();
        Identificator id = null;
        Identificator tempId;
        while (keys.hasMoreElements()) {
            id = keys.nextElement();

            if (compare(localServicesTable.get(id), keyWordArray)) {
                matchingKeys.add(id);
            }
        }

        if (matchingKeys.isEmpty()) {
            tempId = null;
        } else {
            //Ora ritorno solo il primo elemento.
            tempId = matchingKeys.firstElement();
        }
        return tempId;
    }

    /**
     * Returns the description (array of key words) of the service agent
     * registered into the component.
     * 
     * @param serviceAgentId
     *            The identificator of the service agent.
     * 
     * @return The description associated.
     */
    public String[] getServiceDescription(Identificator serviceAgentId) {
        return localServicesTable.get(serviceAgentId);
    }

    /**
     * Sets a service agent identificator into the component.
     * 
     * @param id
     *            The Service Agent Identificator
     * @param keyWords
     *            An array of string that contains the description of the
     *            services.
     * @param propagate
     *            True the registration is propagate on the network False is
     *            only local
     */
    public void setService(Identificator id, String[] keyWords, boolean propagate) {
        if ((identify.getAgentThread(id) != null) && id.isServiceAgent()) {
            localServicesTable.put(id, keyWords);
        } else {
            HermesLogger.log(HermesLogger.WARNING, "Agent:" + id + " registration failed, the agent is not a ServiceAgent!");
        }
        if (propagate) {
            if (descriptorIO != null) {
                ServiceDescriptor sd = new ServiceDescriptor(id.getName(), Place.getCurrentPlaceDescriptor(),arrayToString(keyWords,";"));
                descriptorIO.propagateServiceDescriptor(sd);
            }
        }
    }

    /**
     * This method erases the registration of the service agent from the
     * discovery component.
     * 
     * @param serviceAgentId
     *            The identificator of the service agent that has to be erased.
     */
    public void eraseService(Identificator serviceAgentId) {
        if ((identify.getAgentThread(serviceAgentId) != null) && serviceAgentId.isServiceAgent()) {
            localServicesTable.remove(serviceAgentId);
        } else {
            HermesLogger.log(HermesLogger.WARNING, "Agent:" + serviceAgentId + " Unegistration failed, the agent is not a ServiceAgent!");
        }
    }

    /**
     * This method compares two array of string and checks if the first array
     * has the same or more string of the second.
     * 
     * @param array1
     *            The main array of string with the same or more number of
     *            string.
     * @param array2
     *            The array that contains a subset of string of the first.
     * 
     * @return True if the second array is a subset of the first one, False
     *         otherwise.
     */
    private boolean compare(String[] array1, String[] array2) {
        boolean tempResult = true;
        int i;
        int j;
        i = 0;

        while ((i < array2.length) && tempResult) {
            boolean tempResult2 = false;
            j = 0;

            while ((j < array1.length) || !tempResult) {
                if ((array2[i] != null) && array2[i].equalsIgnoreCase(array1[j])) {
                    tempResult2 = true;
                }

                j++;
            }

            if (!tempResult2) {
                tempResult = false;
            }

            i++;
        }

        return tempResult;
    }

    /**
     * Sets the protocol of a service agent into the component.
     * 
     * @param id
     *            The Service Agent Identificator
     * @param protocol
     *            The protocol of the services.
     */
    public void setServiceProtocol(Identificator id, AbstractProtocol protocol) {
        if ((identify.getAgentThread(id) != null) && id.isServiceAgent()) {
            servicesProtocol.put(id, protocol);
        } else {
            HermesLogger.log(HermesLogger.WARNING, "Agent:" + id + " Registration failed, the agent is not a ServiceAgent!");
        }
    }

    /**
     * This method erases the protocol of the service agent from the discovery
     * component.
     * 
     * @param serviceAgentId
     *            The identificator of the service agent that has to be erased.
     */
    public void eraseServiceProtocol(Identificator serviceAgentId) {
        if ((identify.getAgentThread(serviceAgentId) != null) && serviceAgentId.isServiceAgent()) {
            servicesProtocol.remove(serviceAgentId);
        } else {
            HermesLogger.log(HermesLogger.WARNING, "Agent:" + serviceAgentId + " Unegistration failed, the agent is not a ServiceAgent!");
        }
    }

    /**
     * Returns the protocol of the service agent registered into the component.
     * 
     * @param agentId
     *            The identificator of the service agent.
     * 
     * @return The protocol associated or null.
     */
    public AbstractProtocol getServiceProtocol(Identificator agentId) {
        return servicesProtocol.get(agentId);
    }

    /**
     * Returns the placeAddress of a remote place registered in the discovery
     * component. The match is made with the name.
     * 
     * @param placeName
     *            The name of the remote place.
     * 
     * @return The placeAddress of the remote place corresponding to the name
     *         otherwise if no place match null.
     */
    public PlaceAddress getPlaceAddressFromPlaceName(String placeName) {
        Vector<PlaceAddress> matchingPlaces = new Vector<PlaceAddress>();
        Enumeration<PlaceDescriptor> elements = placesTable.elements();
        PlaceDescriptor pd = null;
        PlaceAddress tempPlaceAddress = null;

        while (elements.hasMoreElements()) {
            pd = elements.nextElement();
            if (pd.getPlaceName().equals(placeName)) {
                matchingPlaces.add(pd.getPlaceAddress());
            }
        }

        if (matchingPlaces.isEmpty()) {
            tempPlaceAddress = null;
        } else {
            //Ora ritorno solo il primo elemento.

            tempPlaceAddress = matchingPlaces.firstElement();
        }
        return tempPlaceAddress;
    }

    /**
     * It starts the thread of the discovery component. First it waits new
     * placeDescriptor from core layer and then it puts it to the
     * placeDescriptorQueu
     */
    public void run() {
        boolean interrupted = false;
        if (descriptorIO == null) {
            HermesLogger.log(HermesLogger.WARNING, "The net component null!");
        } else {
            while (!interrupted) {
                Descriptor descriptor = descriptorIO.receiveDescriptorBloking();
                if (descriptor instanceof PlaceDescriptor) {
                    PlaceDescriptor pd = (PlaceDescriptor) descriptor;
                    pd.setLastPingAlive(System.currentTimeMillis());
                    placesTable.put(pd.getPlaceAddress().toString(), pd);
                } else if (descriptor instanceof ServiceDescriptor) {
                    System.out.println("ServiceDescriptor arrivato!");
                    ServiceDescriptor sd = (ServiceDescriptor) descriptor;
                    sd.setLastPingAlive(System.currentTimeMillis());
                    remoteServicesTable.put(sd.getServiceName(),sd);
                }
            }
        }
    }
    
    /**
     *  Takes an array of tokens and converts into separator-separated string.
     *
     * @param array The array of strings input.
     * @param separators The string separator.
     * @return String A string containing tokens separated by seperator.
     */
    private static final String arrayToString(String[] array, String separators)
    {
        StringBuffer sb = new StringBuffer("");
        String empty = "";
        
        if (array == null)
            return empty;

        if (separators == null)
            separators = ",";

        for (int ix=0; ix < array.length; ix++) 
        {
            if (array[ix] != null && !array[ix].equals("")) 
            {
                sb.append(array[ix] + separators);
            }
        }
        String str = sb.toString();
        if (!str.equals("")) 
        {
            str = str.substring(0, (str.length() - separators.length()));
        }
        return str;
    }

}
